/*
 Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
 Original sources are available at www.latestbit.com

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 RefObject.hpp. Created on: 16.09.2009 12:13:42
 */

#ifndef REFOBJECT_HPP_
#define REFOBJECT_HPP_

#include "cpputils/CppUtilsDefinitions.hpp"
#include "cpputils/SystemException.hpp"
#include <boost/detail/atomic_count.hpp>

namespace cpputils {
	class RefObject;
}

namespace boost {
	inline void intrusive_ptr_add_ref(cpputils::RefObject * p);
    inline void intrusive_ptr_release(cpputils::RefObject * p);
}

namespace cpputils {

class RefObject {
public:
	inline RefObject() : nRefObjectCounter(1)
	#if defined (_DEBUG) && defined(HAVE_BACKTRACE_SYMBOLS)
		, m_bTraceAllOp(false)
	#endif
	{}
	inline virtual ~RefObject() {}

	inline void addRef() {
		++nRefObjectCounter;
#if defined (_DEBUG) && defined(HAVE_BACKTRACE_SYMBOLS)
		if(m_bTraceAllOp) {
			std::cout << this << " (RefObject) " << "ADD_REF" << std::endl;
			SystemException("Dummy").printBacktrace();
		}
#endif
	}

	inline bool release() {
		long nValue = --nRefObjectCounter;
#if defined (_DEBUG) && defined(HAVE_BACKTRACE_SYMBOLS)
		if(m_bTraceAllOp) {
			std::cout << this << " (RefObject) " << "RELEASE" << std::endl;
			SystemException("Dummy").printBacktrace();
		}
#endif
		if ( nValue == 0) {
			delete this; return true;
		}
		else {
			if(nValue < 0) {
				cpputils::SystemException ex(
						boost::format("Internal error: Illegal/unexpected release call detected for already released object (this=%1%)") %this
				);
				std::cout << std::endl;
				std::cout << ex.what() << std::endl;
				ex.printBacktrace();
				std::cout << std::endl;
				throw ex;
			}
			return false;
		}
	}

	inline long getRefCounter() {
		return (long)nRefObjectCounter;
	}

	template<typename T>
	inline static void setNewRefObject(T* &poCurRefObject, T* poSrcRefObject) {
		if(poCurRefObject) {
			poCurRefObject->release();
		}
		poCurRefObject = poSrcRefObject;
		if(poCurRefObject) {
			poCurRefObject->addRef();
		}
	}

	template<typename T>
	inline static void releaseAndClear(T* &poCurRefObject) {
		if(poCurRefObject) {
			poCurRefObject->release();
			poCurRefObject = NULL;
		}
	}

	class SafeForReleaseMethodLock {
	public:
		inline SafeForReleaseMethodLock(RefObject* poRefObject) : m_poRefObject(poRefObject) {
			m_poRefObject->addRef();
		}

		inline ~SafeForReleaseMethodLock() {
			m_poRefObject->release();
		}
	protected:
		RefObject* m_poRefObject;
	};

	template < typename MutexType, typename LockType = boost::unique_lock < MutexType >  >
	class SafeForReleaseMethodMutexLock {
		public:
			inline SafeForReleaseMethodMutexLock(RefObject* poRefObject, LockType &oMutexGuard) : m_poRefObject(poRefObject), m_oMutexGuard(oMutexGuard) {
				m_poRefObject->addRef();
			}

			inline ~SafeForReleaseMethodMutexLock() {
				m_oMutexGuard.unlock();
				m_poRefObject->release();
			}
		protected:
			RefObject* m_poRefObject;
			LockType& m_oMutexGuard;
	};



#if defined (_DEBUG) && defined(HAVE_BACKTRACE_SYMBOLS)
	inline void traceAllRefOps(bool on) {
		m_bTraceAllOp = true;
	}
#endif

private:
	boost::detail::atomic_count nRefObjectCounter;
#if defined (_DEBUG) && defined(HAVE_BACKTRACE_SYMBOLS)
	bool m_bTraceAllOp;
#endif
};

}

namespace boost {
	inline void intrusive_ptr_add_ref(cpputils::RefObject * p) {
		p->addRef();
	}

	inline void intrusive_ptr_release(cpputils::RefObject * p) {
		if(p)
			p->release();
	}
}


#endif /* REFOBJECT_HPP_ */
